package com.meizu.hjcool7.androidorm;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.meizu.hjcool7.androidorm.Util.FieldIterator;
import com.meizu.hjcool7.androidorm.exception.ParseEntityException;

import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;

public class SqliteDB
{
	private SQLiteDatabase mDB;
	private static final String mTag = "SqliteDB";
	private DBThread mDbThread;
	private Handler mDbHandler;
	
	public static boolean debug = false;
	
	private void dLog(String msg)
	{
		if (debug)
		{
			Log.d(mTag,msg);
		}
	}
	
	private void dLog(String msg,Object[] args)
	{
		if (debug)
		{
			String logMsg = msg + " Args:";
			if (args != null)
			{
				for (Object arg : args)
				{
					logMsg += arg.toString() + ",";
				}
			}
			Log.d(mTag, logMsg);
		}
	}
	
	public SqliteDB(SQLiteDatabase db)
	{
		mDB = db;
	}
	
	public SQLiteDatabase getSqliteDatabase()
	{
		return mDB;
	}
	
	public void beginTransaction()
	{
		dLog("beginTransaction");
		mDB.beginTransactionNonExclusive();
	}
	
	public void setTransactionSuccessful()
	{
		dLog("setTransactionSuccessful");
		mDB.setTransactionSuccessful();
	}
	
	public void endTransaction()
	{
		dLog("endTransaction");
		mDB.endTransaction();
	}
	
	public void execSql(String sql)
	{
		dLog("Execute Sql:" + sql);
		mDB.execSQL(sql.toString());
	}
	
	public void execSql(String sql,Object[] args)
	{
		dLog("Execute Sql:" + sql,args);
		mDB.execSQL(sql.toString(),args);
	}
	
	public Cursor rawQuery(String sql)
	{
		dLog("Execute Sql:" + sql);
		return mDB.rawQuery(sql, null);
	}
	
	public Cursor rawQuery(String sql,String[] args)
	{
		dLog("Execute Sql:" + sql,args);
		return mDB.rawQuery(sql,args);
	}
	
	public int getCount(Class<?> entity)
	{
		if (entity == null)
		{
			return 0;
		}
		StringBuffer sql = Util.createStringBuffer("select count(*) from ",Util.getTableName(entity));
		Cursor cursor = rawQuery(sql.toString());
		cursor.moveToNext();
		int count = cursor.getInt(0);
		cursor.close();
		return count;
	}
	
	public <T> List<T> execQuery(Class<T> entity,String query)
	{
		Cursor cursor = rawQuery(query);
		try
		{
			return Util.getResults(cursor, entity);
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public <T> List<T> execQuery(Class<T> entity,String query, String[] args)
	{
		Cursor cursor = rawQuery(query, args);
		try
		{
			return Util.getResults(cursor, entity);
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public <T> T execQueryForSingleResult(Class<T> entity,String query)
	{
		Cursor cursor = rawQuery(query);
		try
		{
			return Util.getResult(cursor, entity);
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public <T> T execQueryForSingleResult(Class<T> entity,String query, String[] args)
	{
		Cursor cursor = rawQuery(query, args);
		try
		{
			return Util.getResult(cursor, entity);
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public List<Map<String, Object>> execQuery(String query)
	{
		Cursor cursor = rawQuery(query);
		return Util.getResults(cursor);
	}
	
	public List<Map<String, Object>> execQuery(String query, String[] args)
	{
		Cursor cursor = rawQuery(query, args);
		return Util.getResults(cursor);
	}
	
	public Map<String, Object> execQueryForSingleResult(String query)
	{
		Cursor cursor = rawQuery(query);
		return Util.getResult(cursor);
	}
	
	public Map<String, Object> execQueryForSingleResult(String query, String[] args)
	{
		Cursor cursor = rawQuery(query,args);
		return Util.getResult(cursor);
	}
	
	public void deleteAll(Class<?> entity)
	{
		if (entity == null)
		{
			return;
		}
		StringBuffer sql = Util.createStringBuffer("delete from ",Util.getTableName(entity));
		execSql(sql.toString());
	}
	
	public void deleteBySql(Class<?> entity,String condition)
	{
		if (entity == null)
		{
			return;
		}
		if (condition == null)
		{
			condition = "";
		}
		StringBuffer sql = Util.createStringBuffer("delete from ",Util.getTableName(entity)," ",condition);
		execSql(sql.toString());
	}
	
	public void deleteByConditions(Class<?> entity,Map<String, Object> condition)
	{
		if (entity == null)
		{
			return;
		}
		StringBuffer sql = Util.createStringBuffer("delete from ",Util.getTableName(entity));
		if (condition != null && condition.size() > 0)
		{
			sql.append(" where ");
		}
		boolean isFirst = true;
		Object[] args = new Object[condition.size()];
		int i = 0;
		for (Entry<String,Object> entry : condition.entrySet())
		{
			isFirst = Util.addString(sql, " and ",isFirst);
			Util.append(sql, entry.getKey(),"=?");
			args[i] = entry.getValue();
			i++;
		}
		execSql(sql.toString(), args);
	}
	
	public <T> T findSingleBySql(Class<T> entity,String conditionSql)
	{
		try
		{
			if (entity == null)
			{
				return null;
			}
			if (conditionSql == null)
			{
				conditionSql = "";
			}
			StringBuffer sql = Util.createStringBuffer("select * from ",Util.getTableName(entity)," ", conditionSql);
			final Cursor cursor = rawQuery(sql.toString(), null);
			T obj = Util.getResult(cursor, entity);
			cursor.close();
			return obj;
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public <T> T findSingleByConditions(Class<T> entity,Map<String, Object> condition)
	{
		try
		{
			if (entity == null)
			{
				return null;
			}
			StringBuffer sql = Util.createStringBuffer("select * from ",Util.getTableName(entity));
			if (condition != null && condition.size() > 0)
			{
				sql.append(" where ");
			}
			boolean isFirst = true;
			String[] args = new String[condition.size()];
			int i = 0;
			for (Entry<String,Object> entry : condition.entrySet())
			{
				isFirst = Util.addString(sql, " and ",isFirst);
				Util.append(sql, entry.getKey(),"=?");
				args[i] = entry.getValue().toString();
				i++;
			}

			final Cursor cursor = rawQuery(sql.toString(), args);
			T obj = Util.getResult(cursor, entity);
			cursor.close();
			return obj;
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public <T> List<T> findByConditionsOrder(Class<T> entity,Map<String, Object> condition,String order)
	{
		try
		{
			if (entity == null)
			{
				return new ArrayList<T>();
			}
			StringBuffer sql = Util.createStringBuffer("select * from ",Util.getTableName(entity));
			String[] args = null;
			if (condition != null && condition.size() > 0)
			{
				sql.append(" where ");
				boolean isFirst = true;
				args = new String[condition.size()];
				int i = 0;
				for (Entry<String,Object> entry : condition.entrySet())
				{
					isFirst = Util.addString(sql, " and ",isFirst);
					Util.append(sql, entry.getKey(),"=?");
					args[i] = entry.getValue().toString();
					i++;
				}
			}
			if (order != null)
			{
				Util.append(sql, " order by ",order);
			}

			final Cursor cursor = rawQuery(sql.toString(), args);
			List<T> result = Util.getResults(cursor, entity);
			cursor.close();
			return result;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			throw new SQLException(e.getMessage());
		}
	}
	
	public <T> List<T> findByConditions(Class<T> entity,Map<String, Object> condition)
	{
		return findByConditionsOrder(entity, condition, null);
	}
	
	public <T> List<T> findAll(Class<T> entity)
	{
		try
		{
			if (entity == null)
			{
				return new ArrayList<T>();
			}
			StringBuffer sql = Util.createStringBuffer("select * from ",Util.getTableName(entity));
			final Cursor cursor = rawQuery(sql.toString(), null);
			List<T> result = Util.getResults(cursor, entity);
			cursor.close();
			return result;
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	public <T> List<T> findBySql(Class<T> entity,String conditionSql)
	{
		try
		{
			if (entity == null)
			{
				return new ArrayList<T>();
			}
			if (conditionSql == null)
			{
				conditionSql = "";
			}
			StringBuffer sql = Util.createStringBuffer("select * from ",Util.getTableName(entity)," ", conditionSql);
			final Cursor cursor = rawQuery(sql.toString(), null);
			List<T> result = Util.getResults(cursor, entity);
			cursor.close();
			return result;
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public <T> T findById(Class<T> entity,Object id)
	{
		try
		{
			if (entity == null || id == null)
			{
				return null;
			}
			StringBuffer sql = Util.createStringBuffer("select * from ",Util.getTableName(entity)," where ",Util.getIdColumnName(entity),"=?");
			final Cursor cursor = rawQuery(sql.toString(),  new String[]{id.toString()});
			T obj = Util.getResult(cursor, entity);
			cursor.close();
			return obj;
		}
		catch (Exception e)
		{
			throw new SQLException(e.getMessage());
		}
	}
	
	public void delete(Object obj)
	{
		if (obj == null)
		{
			return;
		}
		Class<?> entityClass = obj.getClass();
		StringBuffer sql = Util.createStringBuffer("delete from ",Util.getTableName(entityClass)," where ");
		Util.append(sql,Util.getIdColumnName(entityClass),"=?");
		execSql(sql.toString(), new String[]{Util.getIdFieldValueString(obj)});
	}
	
	public void update(Object obj)
	{
		if (obj == null)
		{
			return;
		}
		Class<?> entityClass = obj.getClass();
		StringBuffer sql = Util.createStringBuffer("update ",Util.getTableName(entityClass)," set ");
		Field idField = Util.getIdField(entityClass);
		final ArrayList<String> args = new ArrayList<String>();
		Util.iterateColumn(entityClass, sql, obj, new FieldIterator()
		{
			public void iteratre(StringBuffer sql, Field field, String columnName, ColumnType columnType,Object obj) throws Exception
			{
				Util.append(sql,columnName,"=?");
				args.add(Util.getFieldValueString(field, obj, columnType));
			}
		});
		Util.append(sql, " where ",Util.getColumnName(idField),"=?");
		args.add(Util.getFieldValueString(idField, obj));
		execSql(sql.toString(),args.toArray());
	}
	
	public void save(Object obj)
	{
		if (obj == null)
		{
			return;
		}
		Class<?> entityClass = obj.getClass();
		StringBuffer sql = Util.createStringBuffer("insert into ",Util.getTableName(entityClass)," (");
		Util.iterateColumn(entityClass, sql, null, new FieldIterator()
		{
			public void iteratre(StringBuffer sql, Field field, String columnName, ColumnType columnType,Object obj) throws Exception
			{
				sql.append(columnName);
			}
		});
		sql.append(") values (");
		final ArrayList<String> args = new ArrayList<String>();
		Util.iterateColumn(entityClass, sql, obj,new FieldIterator()
		{
			public void iteratre(StringBuffer sql, Field field, String columnName, ColumnType columnType,Object obj) throws Exception
			{
				sql.append("?");
				args.add(Util.getFieldValueString(field, obj, columnType));
			}
		});
		sql.append(")");
		execSql(sql.toString(),args.toArray());
	}
	
	public void saveOrUpdate(Object obj)
	{
		try
		{
			if (obj == null)
			{
				return;
			}
			Field idField = Util.getIdField(obj.getClass());
			if (idField == null)
			{
				save(obj);
				return;
			}
			idField.setAccessible(true);
			if(findById(obj.getClass(), idField.get(obj).toString()) == null)
			{
				save(obj);
			}
			else
			{
				update(obj);
			}
		}
		catch (Exception e)
		{
			throw new ParseEntityException(e);
		}
	}
	
	public void close()
	{
		mDB.close();
	}
	
	public void doTransaction(DBOperation operation)
	{
		beginTransaction();
		try
		{
			operation.operate();
			setTransactionSuccessful();
		}
		catch (Exception e)
		{
			
			e.printStackTrace();
		}
		endTransaction();
	}
	
	public void createDbThread()
	{
		mDbThread = new DBThread("DBThread");
		mDbThread.start();
		mDbHandler = new Handler(mDbThread.getLooper(), mDbThread);
	}
	
	public void execOnDbThread(DBOperation operation)
	{
		if (mDbThread == null || mDbHandler == null)
		{
			throw new RuntimeException("You should call createDbThread()");
		}
		Message msg = mDbHandler.obtainMessage();
		msg.obj = operation;
		mDbHandler.sendMessage(msg);
	}
	
	public static interface DBOperation
	{
		public void operate() throws Exception;
	}
	
	public static class DBThread extends HandlerThread implements Callback
	{
		public DBThread(String name)
		{
			super(name);
		}

		@Override
		public boolean handleMessage(Message msg)
		{
			Object msgObject = msg.obj;
			if (msgObject instanceof DBOperation)
			{
				DBOperation operation = (DBOperation) msgObject;
				try
				{
					operation.operate();
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
			return true;
		}
	}
}
